#include "mazesolver.h"
#include "libs.h"
#include "LinePlotter.h"

#include <vtkStructuredPointsReader.h>
#include <vtkContourFilter.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkCellPicker.h>
#include <vtkImagePlaneWidget.h>
#include <vtkProperty.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkOutlineFilter.h>
#include <vtkFileOutputWindow.h>
#include <vtkImageData.h>
#include <vtkCommand.h>
#include <vtkTextActor.h>
#include <vtkTextProperty.h>
#include <vtkPlane.h>
#include <vtkCutter.h>
#include <vtkStructuredPoints.h>
#include <vtkPointData.h>
#include <vtkCamera.h>

/*

	Global Space fields

*/

/*
	CHANGE THE 2 PATHS
*/
const char *inputfile = "C:/Users/notebook/Documents/Visual Studio 2010/Projects/VTK Start Code/LoadMaze/maze_2_values.vtk";//input data
const char *outputfile = "C:/Users/notebook/Documents/Visual Studio 2010/Projects/VTK Start Code/VTKRandom/output.txt";//error output

vtkTextActor *textactor;//actor for text menu
vtkTextActor *text_selection;//actor for selection text menu

vtkActor *contouractor;//actor for maze

vtkActor *allPathsActor; // actor for all alternative paths (besides the main path)

vtkImagePlaneWidget *ipw[3];//the 3 cutting planes
vtkImagePlaneWidget *active_ipw = 0;//active cut plane

vtkPlane *planes[3];//the 3 alternative cutting planes
vtkActor *planeactors[6];//and their actors + outline
vtkPlane *active_plane = 0;//active cut plane


vtkRenderer *ren;//renderer
vtkRenderWindow *renWin;//renderwindow

std::vector<std::vector<Segment>> solution;//maze solution

enum selection {saggital, transversal, coronal, opaque, none} sel = none;//what is currently selected for in/decrease

/*
	Callback command for key event handling
*/

class myCommand : public vtkCommand
{
public:
	static myCommand *New()
	{
		return new myCommand;
	}

	virtual void Execute(vtkObject *caller, unsigned long eventid, void *calldata)
	{
		vtkRenderWindowInteractor *rwi = vtkRenderWindowInteractor::SafeDownCast(caller);
		if(!rwi)
			return;

		if(eventid == vtkCommand::KeyPressEvent)
		{
			char c = rwi->GetKeyCode();//get pressed key

			int delta = 0;

			/*
				Static fields, contain application's default settings
			*/

			static bool maze_showed = true, menu_showed = true, imagewidget_selected = true, show_planes = true, all_paths_showed = true;
			static double opacity = 0.5;
			
			switch(c)
			{
			/*
				Increase or decrease selected values
			*/

			case '+' :
					delta = 2;
			case '-' :
				if(sel == opaque)
				{
					//adjust maze opacity
					opacity += 0.05*(delta-1);
					if(opacity < 0.0)
						opacity = 0.0;
					else if(opacity>1.0)
						opacity = 1.0;

					contouractor->GetProperty()->SetOpacity(opacity);
				}
				else if(imagewidget_selected)
				{
					//move cut plane
					if(active_ipw)
					{
						active_ipw->SetSliceIndex(active_ipw->GetSliceIndex()+delta-1);
					}
				}
				else
				{
					if(active_plane)
					{
						//move alternative cut plane
						double origin[3];

						active_plane->GetOrigin(origin);
						origin[0] += delta - 1;
						origin[1] += delta - 1;
						origin[2] += delta -1;

						active_plane->SetOrigin(origin);
					}
				}

				renWin->Render();

				break;

			/*
				Select active cut plane/opacity
			*/

			case 's' :
				active_ipw = ipw[0];
				active_plane = planes[0];
				sel = saggital;
				text_selection->SetInput("Saggital plane");
				renWin->Render();
				break;
			case 't' :
				active_ipw = ipw[1];
				active_plane = planes[1];
				sel = transversal;
				text_selection->SetInput("Transversal plane");
				renWin->Render();
				break;
			case 'c' :
				active_ipw = ipw[2];
				active_plane = planes[2];
				sel = coronal;
				text_selection->SetInput("Coronal plane");
				renWin->Render();
				break;

			case 'o' :
				sel = opaque;
				text_selection->SetInput("Opacity");
				renWin->Render();
				break;

			/*
				Hide maze
			*/

			case 'p' :
				if(maze_showed)
					ren->RemoveActor(contouractor);
				else
					ren->AddActor(contouractor);
				maze_showed = !maze_showed;
				renWin->Render();
				break;

			/*
				Hide/show all  paths
			*/
			case 'n' :
				if(all_paths_showed)
				{
					ren->RemoveActor(allPathsActor);
				}
				else
				{
					ren->AddActor(allPathsActor);
				}
				all_paths_showed = !all_paths_showed;
				renWin->Render();
				break;

			/*
				Hide/show menu
			*/

			case 'h' :
				if(menu_showed)
					ren->RemoveActor2D(textactor);
				else
					ren->AddActor2D(textactor);
				menu_showed = !menu_showed;
				renWin->Render();
				break;


			/*
				Hide/show cut planes
			*/

			case 'x' :
				show_planes = !show_planes;

			/*
				Swap between vtkimageplanewidget and vtkplane
			*/

			case 'b' :
				if(imagewidget_selected)
				{
					for(int i=0; i<3; i++)
					{
						double origin[3];
						ipw[i]->GetOrigin(origin);
						planes[i]->SetOrigin(origin);


						ipw[i]->Off();

						if(show_planes)
						{
							ren->AddActor(planeactors[i]);
							ren->AddActor(planeactors[3+i]);
						}
					}
				}
				else
				{
					for(int i=0; i<3; i++)
					{
						double origin[3];
						planes[i]->GetOrigin(origin);
			
						ipw[i]->SetPlaneOrientation(i);
						ipw[i]->SetSliceIndex(origin[i]);

						if(show_planes)
							ipw[i]->On();

						
						ren->RemoveActor(planeactors[i]);
						ren->RemoveActor(planeactors[3+i]);
					}
				}

				imagewidget_selected = !imagewidget_selected;

				renWin->Render();

				break;

			/*
				Follow solution step by step
			*/

			case 'm' :
				static int i = 0;//current segment index

				float *a = solution[0][i].point;
				float *b = solution[0][i+1].point;

				int r= 10;//watch from some distance

				//adjust camera
				vtkCamera *camera = ren->GetActiveCamera();
				camera->SetPosition(a[0]-r, a[1]-r, a[2]-r);
				camera->SetFocalPoint(b[0]+0.5, b[1]+0.5, b[2]+0.5);

				//calculate a suitable up direction for camera
				float u = b[0] - a[0], v = b[1] - a[1], w = b[2] - a[2];
				float uu, vv, ww;

				if(w)
				{
					uu = 1.0f + r*u;
					vv = 0.0f + r*v;
					ww = -u/w + r*w;
				}
				else if(v)
				{
					uu = 1.0f + r*u;
					vv = -u/v + r*v;
					ww = 0.0f + r*w;
				}
				else
				{
					uu = -v/u + r*u;
					vv = 1.0f + r*v;
					ww = 0.0f + r*w;
				}

				camera->SetViewUp(uu, vv, ww);


				//move to the next point, so as long as 3 points are collinear continue
				static int m=0;

				while(true)
				{
					i++;
					m++;

					if(i==solution[0].size()-2)
					{
						i = 0;
						break;
					}


					float *a = solution[0][i].point;
					float *b = solution[0][i+1].point;

					if(u == b[0] - a[0] && v == b[1] - a[1] && w == b[2] - a[2])
						continue;

					break;
				}

				renWin->Render();
			}
		}
	}
};

int main()
{
	/*
		Redirect default error output to textfile
	*/

	vtkFileOutputWindow *fileOutputWindow = vtkFileOutputWindow::New();
	fileOutputWindow->SetFileName(outputfile);
	vtkOutputWindow::SetInstance(fileOutputWindow);

	/*
		Read data from vtk file
	*/

	vtkStructuredPointsReader *reader = vtkStructuredPointsReader::New();
	reader->SetFileName(inputfile);
    reader->Update();

	/*
		Set up the maze
	*/

	vtkStructuredPoints *structuredpoints = reader->GetOutput();
	
	int* dims = structuredpoints->GetDimensions();

	unsigned char ***grid = new unsigned char **[dims[0]];

	unsigned char maximum_value = 1.0f;//keep track of the maximum cost
 
	for(int i=0; i<dims[0]; i++)
	{
		grid[i] = new unsigned char *[dims[1]];

		for(int j=0; j<dims[1]; j++)
		{
			grid[i][j] = new unsigned char [dims[2]];

			for(int k=0; k<dims[2]; k++)
			{
				unsigned char pixel = *((unsigned char*)(structuredpoints->GetScalarPointer(k,j,i)));
				
				grid[i][j][k] = pixel;

				if(pixel>maximum_value)
					maximum_value = pixel;
			}
		}
	}

	Maze *maze = new Maze(dims[0], dims[1], dims[2], grid);//create maze

	for(int i=0; i<dims[0]; i++)
	{
		for(int j=0; j<dims[1]; j++)
		{
			delete[] grid[i][j];
		}

		delete [] grid[i];
	}

	delete[] grid;

	/*
		Solve the maze
	*/
	solution = maze->solve(Coordinates(68,0,0), Coordinates(76,104,104));//calculate solution
	delete maze;

	/*
		Generate image data for cut planes
	*/

	vtkImageData *id = (vtkImageData*)(reader->GetOutput());
	 
	/*
		Generate actor using marchingcubes (contourfilter)
	*/

	vtkContourFilter *contourfilter = vtkContourFilter::New();
	contourfilter->SetInputConnection(reader->GetOutputPort());
	contourfilter->SetValue(0, 1.0);
	contourfilter->Update();

	vtkPolyDataMapper *contourmapper = vtkPolyDataMapper::New();
	contourmapper->SetInput(contourfilter->GetOutput());

	contouractor = vtkActor::New();
	contouractor->SetMapper(contourmapper);
	contouractor->GetProperty()->SetColor(0.3, 0.5, 0.9);
	contouractor->GetProperty()->SetOpacity(0.5);

	/*
		Generate actor for outline
	*/

	vtkOutlineFilter *outlinefilter = vtkOutlineFilter::New();
	outlinefilter->SetInput(contourfilter->GetOutput());

	vtkPolyDataMapper *outlinemapper = vtkPolyDataMapper::New();
	outlinemapper->SetInputConnection(outlinefilter->GetOutputPort());

	vtkActor *outlineactor = vtkActor::New();
	outlineactor->SetMapper(outlinemapper);
	outlineactor->GetProperty()->SetColor(1.0, 1.0, 1.0);

	/*
		 Generate text menu
	*/

	textactor = vtkTextActor::New();
	textactor->GetTextProperty()->SetFontSize (24);
	textactor->GetTextProperty()->SetColor (1.0,1.0,1.0);
	textactor->SetPosition(0.0, 0.0);
	
	textactor->SetInput(
		"Press s, t, c to select saggital, transversal and coronal cut planes"
		"\nPress o to select opacity"
		"\nPress +/- to increse/decrease selection"
		"\nPress p to show/hide the maze"
		"\nPress n to show/hide all alternative paths"
		"\nPress x to show/hide cut planes"
		"\nPress b to switch planes"
		"\nPress m for navigation"
		"\nPress h to show/hide this menu"
	);
	

	text_selection = vtkTextActor::New();
	text_selection->GetTextProperty()->SetFontSize(22);
	text_selection->GetTextProperty()->SetColor(1.0, 1.0, 1.0);
	text_selection->SetPosition(0.0, 500.0);
	text_selection->SetInput("nothing selected");

	/*
		Generate renderer, add actors
	*/

	ren = vtkRenderer::New();
	ren->SetBackground(0.1, 0.1, 0.2);
	ren->AddActor(outlineactor);
	ren->AddActor(contouractor);
	ren->AddActor2D(textactor);
	ren->AddActor2D(text_selection);

	/*
		Generate renderwindow, interactor, add callback
	*/

	renWin = vtkRenderWindow::New();
	renWin->SetSize(600, 600);
	renWin->AddRenderer(ren);

	vtkRenderWindowInteractor *renderWindowInteractor = vtkRenderWindowInteractor::New();
    renderWindowInteractor->SetRenderWindow(renWin);
	renderWindowInteractor->Initialize();

	myCommand *mycommand = myCommand::New();
	renderWindowInteractor->AddObserver(vtkCommand::KeyPressEvent, mycommand);

	renWin->Render();

	vtkCellPicker *picker = vtkCellPicker::New();
	picker->SetTolerance(0.005);

	for(int i=0; i<3; i++)
	{
		/*
			Generate cutting planes
		*/

		vtkImagePlaneWidget *planeWidget = vtkImagePlaneWidget::New();
		ipw[i] = planeWidget;

		double normal[] = {0.0, 0.0, 0.0};
		normal[i] = 1.0;

		planeWidget->DisplayTextOn();
		planeWidget->SetInput(id);
		planeWidget->SetSliceIndex(0);
		planeWidget->SetPicker(picker);
		planeWidget->GetPlaneProperty()->SetColor(normal);
		planeWidget->TextureInterpolateOn();
		planeWidget->SetResliceInterpolateToNearestNeighbour();
		planeWidget->SetDefaultRenderer(ren);
		planeWidget->SetInteractor(renWin->GetInteractor());
		planeWidget->SetPlaneOrientation(i);
		planeWidget->On();
		planeWidget->SetInteraction(false);

		/*
			Generate alternative cutting planes
		*/

		planes[i] = vtkPlane::New();
        planes[i]->SetOrigin(0.0, 0.0, 0.0);
        planes[i]->SetNormal(normal);


        vtkCutter *planeCut = vtkCutter::New();
		planeCut->SetInput(contourfilter->GetOutput());
        planeCut->SetCutFunction(planes[i]);

        vtkPolyDataMapper *mapper = vtkPolyDataMapper::New();
        mapper->SetInputConnection(planeCut->GetOutputPort());
        mapper->ScalarVisibilityOff();

        vtkActor *actor = vtkActor::New();
		planeactors[i] = actor;
        actor->GetProperty()->SetColor(normal);
        actor->SetMapper(mapper);actor->GetProperty()->LightingOff();

        //generate outline for alternative cutting planes
        vtkOutlineFilter *outline = vtkOutlineFilter::New();
        outline->SetInputConnection(planeCut->GetOutputPort());
 
        vtkPolyDataMapper *outlineMapper = vtkPolyDataMapper::New();
        outlineMapper->SetInputConnection(outline->GetOutputPort());
 
        vtkActor *outlineActor = vtkActor::New();
		planeactors[3+i] = outlineActor;
        outlineActor->SetMapper(outlineMapper);
        outlineActor->GetProperty()->SetColor(normal);
	}

	/*
		Draw the solution
	*/

	LinePlotter lineplotter;
	lineplotter.SetAllLineWidth(5);
	lineplotter.PlotLine(solution[0]);
	lineplotter.SetScalarRange(0, maximum_value);

	vtkActor *lineactor = lineplotter.CreateActor();
	ren->AddActor(lineactor);

	/*
		Draw all alternative solutions
	*/

	LinePlotter lineplotterAlternative;
	lineplotterAlternative.SetAllLineWidth(5);
	for(unsigned int i = 1; i < solution.size(); i++)
	{
		lineplotterAlternative.PlotLine(solution[i]);
	}
	lineplotterAlternative.SetScalarRange(0, maximum_value);

	allPathsActor = lineplotterAlternative.CreateActor();
	ren->AddActor(allPathsActor);

	/*
		Start VTK
	*/

	renderWindowInteractor->Start();

	return 0;
}
